package net.lovesrc.allin.biz.lbc;

import com.alibaba.fastjson.JSONObject;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.zhouzhou.fastjava.BizException;
import com.zhouzhou.fastjava.http.MyHttpRequest;
import com.zhouzhou.fastjava.http.Requests;
import com.zhouzhou.fastjava.util.StringKit;
import com.zhouzhou.fastjava.util.ThreadKit;
import net.lovesrc.allin.biz.BaseService;
import net.lovesrc.support.service.imageverifycode.FeiFeiImageVerifyCodeService;
import net.lovesrc.support.service.imageverifycode.ImageVerifyCodeService;
import net.lovesrc.support.service.mobileverifycode.MobileVerifyCodeService;
import net.lovesrc.support.service.mobileverifycode.MobileVerifyCodeWrapper;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.http.HttpHost;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author gelic
 * Created in 22:38 2019/2/21
 * @since
 */
public class BaseLbcService extends BaseService {

    protected Cache<String, HttpClient> httpClientCache = CacheBuilder
            .newBuilder()
            .expireAfterAccess(30, TimeUnit.MINUTES)
            .maximumSize(2048)
            .build();

    /**
     * 代理枚举
     */
    public enum Proxy {
        /**
         * 无代理
         */
        NO_PROXY("无代理"),
        PROXY_GELIC_ALIYUN_59("47.101.155.59"),
        PROXY_MIKE("mike"),
        PROXY_GELIC("gelic");
        private String name;

        Proxy(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

    @Autowired
    protected MobileVerifyCodeService mobileVerifyCodeService;

    @Value("${tess.datapath}")
    private String tessDataPath = "/data/tess";

    protected static final Long REQUEST_MILLSECOND_INTERVAL = 1000L;

    private ImageVerifyCodeService imageVerifyCodeService2;

    protected synchronized ImageVerifyCodeService getImageVerifyCodeService() {
        if (imageVerifyCodeService2 == null) {
            imageVerifyCodeService2 = new ImageVerifyCodeService() {
                Captcha captcha = new Captcha(tessDataPath);

                @Override
                public String recoginze(String contentType, InputStream is) {
                    try {
                        return captcha.process(is);
                    } catch (Exception e) {
                        logger.error(e.getMessage(), e);
                        throw new BizException("ERROR", "识别图形验证码错误", e);
                    }
                }

                @Override
                public String recoginze(String type, String contentType, InputStream is) {
                    return new FeiFeiImageVerifyCodeService("110923", "UuAsekKDku93sX9Viv8Qj+wY8SdRxVWW").recoginze(type, contentType, is);
                }
            };
        }
        return imageVerifyCodeService2;
    }

    protected MobileVerifyCodeWrapper getMobileVerifyCodeWrapper() {
        return new MobileVerifyCodeWrapper(mobileVerifyCodeService, "32142");
    }

    protected void checkResJson(JSONObject resJson) {
        String status = resJson.getString("status");
        if ("success".equals(status)) {
            // is okay
        } else {
            throw BizException.messageOf(resJson.getString("message"));
        }
    }

    protected void checkResHtml(String resHtml) {
        if (resHtml.contains("Too many requests")) {
            throw BizException.messageOf("请求过于频繁");
        } else {
        }
    }

    private void doLogin(String mobile, Proxy proxy) {
        JSONObject row = getAndCheckRow(mobile);
        //1token如果可以复用,检查身份状态,返回
        //2token不可复用,准备登陆信息
        HttpClient httpClient = getHttpClient(mobile, Proxy.PROXY_MIKE);
        String html = MyHttpRequest.get(httpClient, "https://www.bluebeicoin.com")
                .printResText(false)
                .executeUtilReturnAndWaitMillSecond(REQUEST_MILLSECOND_INTERVAL, 200)
                .asText();

        //获取token
        String formToken = Jsoup.parse(html)
                .body()
                .select("input[type=hidden]")
                .select("[name=_token]")
                .select("[value]")
                .first()
                .attr("value");
        String VERIFY_CODE_URL = Jsoup.parse(html)
                .body()
                .select("img.verification")
                .select("[onclick]")
                .select("[src]")
                .first()
                .attr("src");
        //2.1用户名,密码
        boolean getCode = false;
        String captcha;
        for (int i = 1; i < 10; i++) {
            //2.2获取图形验证码,并识别
            byte[] captchaBytes = MyHttpRequest.get(httpClient, VERIFY_CODE_URL)
                    .printResText(false)
                    .executeUtilReturnAndWaitMillSecond(REQUEST_MILLSECOND_INTERVAL, 200)
                    .asBytes();
            if (i < 5) {
                captcha = getImageVerifyCodeService().recoginze("image/png", new ByteArrayInputStream(captchaBytes));
            } else {
                captcha = getImageVerifyCodeService().recoginze("10400", "image/png", new ByteArrayInputStream(captchaBytes));
            }

            //2.3登陆
            JSONObject resJson = MyHttpRequest.post(httpClient, "https://www.bluebeicoin.com/login")
                    .param("_token", formToken)
                    .param("phone", row.getString("mobile"))
                    .param("password", row.getString("password"))
                    .param("captcha", captcha)
                    .executeUtilReturnAndWaitMillSecond(REQUEST_MILLSECOND_INTERVAL, 200)
                    .asJson();
            if ("success".equals(resJson.getString("status"))) {
                getCode = true;
                break;
            }
        }
        if (!getCode) {
            throw BizException.messageOf("未能识别出图形验证码");
        }

        //2.4入库 无需保存什么

        //记录登录日志,方便统计一下
        insertRow(jdbcTemplate, "t_lbc_login",
                new JSONObject().fluentPut("mobile", row.getString("mobile")).fluentPut("cookies", Requests.toCookieString(getCookies(mobile))));
        logger.info("{} 重新登录了 {}", row.getString("mobile"), new Date());
    }

    protected JSONObject getAndCheckRow(String mobile) {
        JSONObject row = queryForRow(jdbcTemplate, "select * from t_lbc_account where mobile = ?", mobile);
        if (row == null) {
            throw BizException.messageOf("{} 在系统中不存在", mobile);
        }
        return row;
    }

    protected String get2Text(String mobile, String url, Proxy proxy) {
        return get2Text(mobile, url, new JSONObject(), proxy);
    }

    protected String get2Text(String mobile, String url, JSONObject params, Proxy proxy) {
        try {
            ThreadKit.sleepMilliseconds(REQUEST_MILLSECOND_INTERVAL);
            HttpClient httpClient = getHttpClient(mobile, proxy);
            String resText = MyHttpRequest.get(httpClient, url)
                    .params(params)
                    .printResText(false)
                    .executeUtilReturnAndWaitMillSecond(REQUEST_MILLSECOND_INTERVAL, 200, 302)
                    .asText();

            if (resText.contains("<center>mozhedun</center>")) {
                ThreadKit.sleepMilliseconds(REQUEST_MILLSECOND_INTERVAL);
                resText = MyHttpRequest.get(getHttpClient(mobile, proxy), url)
                        .params(params)
                        .printResText(false)
                        .executeUtilReturnAndWaitMillSecond(REQUEST_MILLSECOND_INTERVAL, 200, 302)
                        .asText();
            }

            if (resText.contains("<title>登录</title>")
                    || resText.contains("<title>Redirecting to https://www.bluebeicoin.com</title>")) {
                ThreadKit.sleepMilliseconds(REQUEST_MILLSECOND_INTERVAL);
                // 未登录,登录重试
                doLogin(mobile, proxy);
                return MyHttpRequest.get(getHttpClient(mobile, proxy), url)
                        .params(params)
                        .printResText(false)
                        .executeUtilReturnAndWaitMillSecond(REQUEST_MILLSECOND_INTERVAL, 200)
                        .asText();
            }
            checkResHtml(resText);
            return resText;
        } finally {
            saveCookies(mobile, getCookies(mobile));
        }
    }

    protected JSONObject post2Json(String mobile, String url, JSONObject params, Proxy proxy) {
        try {
            ThreadKit.sleepMilliseconds(REQUEST_MILLSECOND_INTERVAL);
            return MyHttpRequest.post(getHttpClient(mobile, proxy), url)
                    .params(params)
                    .executeUtilReturnAndWaitMillSecond(REQUEST_MILLSECOND_INTERVAL, 200)
                    .asJson();
        } finally {
            saveCookies(mobile, getCookies(mobile));
        }
    }


    protected String getFormToken(String mobile, String url, Proxy proxy) {
        String resText = get2Text(mobile, url, proxy);
        String formToken = Jsoup.parse(resText)
                .body()
                .select("input[type=hidden]")
                .select("[name=_token]")
                .select("[value]")
                .first()
                .attr("value");

        return formToken;
    }

    protected String getHtmlToken(String mobile, String url, Proxy proxy) {
        String resText = get2Text(mobile, url, proxy);
        String token = Jsoup.parse(resText)
                .select("meta[name=_token]")
                .attr("content");

        return token;
    }

    protected synchronized HttpClient getHttpClient(String mobile, Proxy proxy) {
        HttpClient httpClient = httpClientCache.getIfPresent(mobile);
        if (httpClient == null) {
            httpClient = getHttpClient2(mobile, proxy);
            httpClientCache.put(mobile, httpClient);
        }
        return httpClient;
    }

    private HttpClient getHttpClient2(String mobile, Proxy proxy) {
        HttpClient httpClient;
        switch (proxy) {
            case PROXY_MIKE:
                httpClient = newHttpClientWithMyProxy(null, getCookies(mobile));
                break;
            case PROXY_GELIC:
                httpClient = newHttpClientWithGelicProxy(null, getCookies(mobile));
                break;
            case PROXY_GELIC_ALIYUN_59:
                httpClient = newHttpClientBuilder(getCookies(mobile))
                        .setProxy(new HttpHost("47.101.155.59", 18028))
                        .build();
                break;
            case NO_PROXY:
                httpClient = newHttpClientBuilder(getCookies(mobile)).build();
                break;
            default:
                httpClient = newHttpClientBuilder(getCookies(mobile)).build();
        }
        return httpClient;
    }

    protected synchronized void saveCookies(String mobile, CookieStore cookieStore) {
        cookieStoreCache.put(mobile, cookieStore);
        updateRow(jdbcTemplate, "t_lbc_account", new JSONObject().fluentPut("cookies", Requests.toCookieString(cookieStore))
                .fluentPut("tokenTime", new Date()), "mobile=?", mobile);
    }

    protected synchronized CookieStore getCookies(String mobile) {
        CookieStore cookieStore = cookieStoreCache.getIfPresent(mobile);
        if (cookieStore == null) {
            JSONObject row = queryForRow(jdbcTemplate, "select cookies from t_lbc_account where mobile = ?", mobile);
            cookieStore = Requests.toCookieStore(row.getString("cookies"));
            cookieStoreCache.put(mobile, cookieStore);
        }
        return cookieStore;
    }

    public boolean isNotJyTime() {
        return !isJyTime();
    }

    public boolean isJyTime() {
        String jyTime = tryGetProperties(String.class, "10-17", "Lbc_Jy_Time");
        int minJyTime = Integer.valueOf(jyTime.split("-")[0]);
        int maxJyTime = Integer.valueOf(jyTime.split("-")[1]);
        int nowTime = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
        return nowTime >= minJyTime && nowTime < maxJyTime;
    }

    public List<JSONObject> getMyKjs(String mobile, Proxy proxy) {
        List<Element> elements = doGetMyKjDoms(mobile, proxy);
        List<JSONObject> kjs = Lists.newArrayList();
        for (Element element : elements) {
            String text = element.selectFirst("div.shop_text").text();
            if (text.contains("已过期")) {
                continue;
            }
            String name = StringKit.extract(text, "(.{2})矿机(.)+") + "矿机";
            Elements btns = element.select("a.my_mill_btn1").select("[href^='/myminers/']");
            int id = NumberUtils.toInt(btns.get(0).attr("data-id"));
            if (id <= 0) {
                throw BizException.messageOf("{} 无效的矿机id", id);
            }
            String status = btns.get(0).text();
            //运行 查看 todo 结束
            if (status.contains("运行")) {
                status = "未使用";
            } else if (status.contains("查看")) {
                status = "运行中";
            }
            kjs.add(
                    new JSONObject()
                            .fluentPut("id", id)
                            .fluentPut("name", name)
                            .fluentPut("status", status)
                            .fluentPut("text", text)
            );
        }
        kjs.stream()
                .filter(kj -> isNotUsed(kj))
                .forEach(kj -> runningKj(mobile, kj, proxy));
        updateUserKjs(mobile, kjs);
        return kjs;
    }

    // 运行中和未使用
    protected boolean isWorkKj(JSONObject row) {
        return isRunningKj(row) || isNotUsed(row);
    }

    // 运行中
    private boolean isRunningKj(JSONObject row) {
        String status = row.getString("status");
        return status.contains("运行中");
    }

    private boolean isNotUsed(JSONObject row) {
        String status = row.getString("status");
        return status.contains("未使用");
    }

    private List<Element> doGetMyKjDoms(String mobile, Proxy proxy) {
        List<Element> doms = Lists.newArrayList();
//        for (int page = 1; page <= 100; page++) {
//            String kjStr = get2Text(row, "https://www.bluebeicoin.com/myminers", cookies, proxy);
//            Elements elements = Jsoup.parse(kjStr)
//                    .select("div.shop_wrap ul.shop_ul li.shop_li");
//
//            String content = resJson.getString("content");
//            if (StringUtils.isBlank(content) || content.contains("暂无记录")) {
//                break;
//            }
//
//            Document doc = Jsoup.parse(content);
//            Elements pageItems = doc.select(".item");
//            doms.addAll(pageItems);
//            if (pageItems.size() < 10) {
//                break;
//            }
//        }
        String kjStr = get2Text(mobile, "https://www.bluebeicoin.com/myminers", proxy);
        Elements elements = Jsoup.parse(kjStr)
                .select("div.shop_wrap ul.shop_ul li.shop_li");
        doms.addAll(elements);
        return doms;
    }

    private void runningKj(String mobile, JSONObject row, Proxy proxy) {
        get2Text(mobile, "https://www.bluebeicoin.com/myminers/run/" + row.getString("id"), proxy);
        row.put("status", "运行中");
    }

    private void updateUserKjs(String mobile, List<JSONObject> kjs) {
        for (JSONObject kj : kjs) {
            JSONObject row = new JSONObject()
                    .fluentPutAll(kj);
            row.put("mobile", mobile);
            int id = kj.getIntValue("id");
            if (jdbcTemplate.queryForObject("select count(1) from t_lbc_kj where id = ?", long.class, id) == 0) {
                insertRow(jdbcTemplate, "t_lbc_kj", row);
            } else {
                row.remove("id");
                updateRow(jdbcTemplate, "t_lbc_kj", row, "id = ?", id);
            }
        }
    }
}
